package edu.eas.auth.config;

import edu.eas.auth.component.JwtTokenEnhancer;
import edu.eas.auth.service.impl.UserServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;

/**
 * 认证服务器配置
 *
 * @author cql
 */
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserServiceImpl userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    /**
     * 用来配置客户端详情服务（ClientDetailsService），客户端详情信息在这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
     * <p>
     * ClientDetailsServiceConfigurer 能够使用内存或者JDBC来实现客户端详情服务（ClientDetailsService），ClientDetailsService负责查找ClientDetails，而ClientDetails有几个重要的属性如下列表：
     * (1)scopes: 授权范围标识，比如指定微服务名称，则只可以访问指定的微服务
     * (2)withClient: 允许访问此认证服务器的客户端ID
     * (3)secret: 客户端密码，加密存储
     * (4)authorizedGrantTypes: 授权类型，支持同时多种授权类型(authorization_code, password, implicit,client_credentials,refresh_token)
     * (5)authorities: 此客户端可以使用的权限（基于Spring Security authorities）
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // TODO 还是不够完美,每一次都需要去数据库查,虽然登录不会出现什么高平发场景,但是是否可以使用Redis优化呢
        clients.jdbc(dataSource);
        // 后期将这个配置放入到数据库中
        // clients.inMemory()
        //         .withClient("admin-app")
        //         .secret(passwordEncoder.encode("123456"))
        //         .scopes("all")
        //         .authorizedGrantTypes("password", "refresh_token")
        //         .accessTokenValiditySeconds(3600 * 24)
        //         .refreshTokenValiditySeconds(3600 * 24 * 7)
        //         .and()
        //         .withClient("student-app")
        //         .secret(passwordEncoder.encode("123456"))
        //         .scopes("all")
        //         .authorizedGrantTypes("password", "refresh_token")
        //         .accessTokenValiditySeconds(3600 * 24)
        //         .refreshTokenValiditySeconds(3600 * 24 * 7)
        //         .and()
        //         .withClient("teacher-app")
        //         .secret(passwordEncoder.encode("123456"))
        //         .scopes("all")
        //         .authorizedGrantTypes("password", "refresh_token")
        //         .accessTokenValiditySeconds(3600 * 24)
        //         .refreshTokenValiditySeconds(3600 * 24 * 7);
    }

    /**
     * 用来配置令牌（token）的访问端点和令牌服务(tokenServices)
     * <p>
     * AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型（Grant Types）:
     * (1)authenticationManager：认证管理器，当你选择了资源所有者密码（password）授权类型的时候，请设置
     * 这个属性注入一个 AuthenticationManager 对象
     * <p>
     * (2)userDetailsService：如果你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现，
     * 或者你可以把这个东西设置到全局域上面去（例如 GlobalAuthenticationManagerConfigurer 这个配置对
     * 象），当你设置了这个之后，那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查，用
     * 来确保这个账号是否仍然有效，假如说你禁用了这个账户的话。
     * <p>
     * (3)authorizationCodeServices：这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对
     * 象），主要用于 "authorization_code" 授权码类型模式
     * (4)implicitGrantService：这个属性用于设置隐式授权模式，用来管理隐式授权模式的状态
     * (5)tokenGranter：当你设置了这个东西（即 TokenGranter 接口实现），那么授权将会交由你来完全掌控，并
     * 且会忽略掉上面的这几个属性，这个属性一般是用作拓展用途的，即标准的四种授权模式已经满足不了你的
     * 需求的时候，才会考虑使用这个。
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(accessTokenConverter());
        // 配置JWT的内容增强器
        enhancerChain.setTokenEnhancers(delegates);
        endpoints.authenticationManager(authenticationManager)
                // 配置加载用户信息的服务
                .userDetailsService(userDetailsService)
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);
    }

    /**
     * 令牌端点,即访问获取令牌的URL,默认为 /oauth/token
     * 用来配置令牌端点的安全约束(即怎么样的客户端才可以访问令牌端点)
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 校验令牌合法性(一般不开放)
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }

    /**
     * Spring Cloud Config的非对称加密可以使用RSA加密方式，通过JDK自带的keytool生产秘匙对，对敏感信息进行加密解密。
     *
     * @return
     */
    @Bean
    public KeyPair keyPair() {
        // 从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }


}
